package org.dizem.freepl0.compiler;

import org.dizem.freepl0.compiler.targetcodemanager.TargetCodeManager;
import org.dizem.freepl0.compiler.targetcodemanager.Fct;
import org.dizem.freepl0.compiler.errormanager.ErrorManager;
import org.dizem.freepl0.compiler.symbol.Symbol;
import org.dizem.freepl0.compiler.tablemanager.Table;
import org.dizem.freepl0.compiler.tablemanager.TableManager;
import org.dizem.freepl0.compiler.tablemanager.TableType;

import java.util.HashSet;
import java.util.Set;

/**
 * Created by IntelliJ IDEA.
 * User: DIZEM
 * Date: 11-2-2
 * Time: 下午2:51
 */
public class GrammaAnalysis {

	public static int MAX_LEVEL = 3;
	private Symbol sym;
	private int[] data = new int[MAX_LEVEL + 1];
	private int level;

	private Set<Symbol> decBeginSym = new HashSet<Symbol>();
	private Set<Symbol> stateBeginSym = new HashSet<Symbol>();
	private Set<Symbol> expBeginSym = new HashSet<Symbol>();

	private WordAnalysis wordAnalysis;
	private ErrorManager errorManager;
	private TableManager tableManager;
	private TargetCodeManager targetCodeManager;

	public GrammaAnalysis(FPCompiler compiler) {

		wordAnalysis = compiler.getWordAnalysis();
		errorManager = compiler.getErrorManager();
		tableManager = compiler.getTableManager();
		targetCodeManager = compiler.getTargetCodeManager();


		decBeginSym.add(Symbol.CONSTSYM);
		decBeginSym.add(Symbol.VARSYM);
		decBeginSym.add(Symbol.PROCSYM);

		stateBeginSym.add(Symbol.BEGINSYM);
		stateBeginSym.add(Symbol.CALLSYM);
		stateBeginSym.add(Symbol.IFSYM);
		stateBeginSym.add(Symbol.WHILESYM);
		stateBeginSym.add(Symbol.REPEATSYM);

		expBeginSym.add(Symbol.IDENT);
		expBeginSym.add(Symbol.NUMBER);
		expBeginSym.add(Symbol.LPAREN);
	}


	public void analysis() {
		getSymbol();
		level = -1;
		Set<Symbol> s = new HashSet<Symbol>();
		s.addAll(decBeginSym);
		s.addAll(stateBeginSym);
		s.add(Symbol.PERIOD);
		block(s);
	}


	private void modify(Set<Symbol> s1, Set<Symbol> s2, int n) {

		if (!s1.contains(sym)) {
			errorManager.addError(n);

			while ((!s1.contains(sym) && (!s2.contains(sym))) && sym != Symbol.ENDFILE) {
				getSymbol();
			}
		}
	}


	private void block(Set<Symbol> symbolSet) {
		level++;
		Set<Symbol> s = new HashSet<Symbol>();
		data[level] = 3;
		int tx0 = tableManager.getTableIndex();

		tableManager.getTable()[tx0].setAddress(targetCodeManager.getCodeIndex());
		targetCodeManager.addCode(Fct.JMP, 0, 0);

		if (level > MAX_LEVEL) {
			errorManager.addError(32);

		}
		do {
			if (sym == Symbol.CONSTSYM) {
				getSymbol();

				do {
					constDeclaration();

					while (sym == Symbol.COMMA) {
						getSymbol();
						constDeclaration();
					}
					if (sym == Symbol.SEMICOLON) {
						getSymbol();
					} else {
						errorManager.addError(5);
					}

				} while (sym == Symbol.IDENT);
			}

			if (sym == Symbol.VARSYM) {
				getSymbol();

				do {
					varDeclaration();

					while (sym == Symbol.COMMA) {

						getSymbol();
						varDeclaration();
					}

					if (sym == Symbol.SEMICOLON) {
						getSymbol();

					} else {

						errorManager.addError(5);
					}

				} while (sym == Symbol.IDENT);
			}

			while (sym == Symbol.PROCSYM) {

				getSymbol();

				if (sym == Symbol.IDENT) {
					tableManager.Enter(TableType.PROCEDURE);
					getSymbol();

				} else {
					errorManager.addError(4);
				}

				if (sym == Symbol.SEMICOLON) {
					getSymbol();

				} else {
					errorManager.addError(5);
				}

				s.clear();
				s.addAll(symbolSet);
				s.add(Symbol.SEMICOLON);
				block(s);

				if (sym == Symbol.SEMICOLON) {
					getSymbol();
					s.clear();
					s.addAll(stateBeginSym);
					s.add(Symbol.IDENT);
					s.add(Symbol.PROCSYM);
					modify(s, symbolSet, 6);

				} else {
					errorManager.addError(5);
				}
			}
			s.clear();
			s.addAll(stateBeginSym);
			s.add(Symbol.IDENT);
			s.add(Symbol.PERIOD);
			modify(s, decBeginSym, 7);

		} while (decBeginSym.contains(sym));

		targetCodeManager.code[tableManager.getTable()[tx0].getAddress()].setAddress(targetCodeManager.getCodeIndex());
		tableManager.getTable()[tx0].setAddress(targetCodeManager.getCodeIndex());
		tableManager.getTable()[tx0].setSize(data[level]);
		targetCodeManager.addCode(Fct.INT, 0, data[level]);

		s.clear();
		s.addAll(symbolSet);
		s.add(Symbol.SEMICOLON);
		s.add(Symbol.ENDSYM);
		statement(s);
		targetCodeManager.addCode(Fct.OPR, 0, 0);

		s.clear();
		modify(symbolSet, s, 8);
		level--;
	}

	private void constDeclaration() {

		if (sym == Symbol.IDENT) {

			getSymbol();

			if (sym == Symbol.EQL || sym == Symbol.BECOMES) {

				if (sym == Symbol.BECOMES) {
					errorManager.addError(1);
				}
				getSymbol();
				if (sym == Symbol.NUMBER) {
					tableManager.Enter(TableType.CONSTANT);
					getSymbol();

				} else {
					errorManager.addError(2);
				}

			} else {
				errorManager.addError(3);
			}

		} else {
			errorManager.addError(4);
		}
	}

	private void varDeclaration() {

		if (sym == Symbol.IDENT) {

			tableManager.Enter(TableType.VARIABLE);
			data[level]++;
			getSymbol();

		} else {
			errorManager.addError(4);
		}
	}

	private void statement(Set<Symbol> symbolSet) {
		int i, index1, index2;
		Table[] table = tableManager.getTable();
		HashSet<Symbol> s = new HashSet<Symbol>();

		switch (sym) {

			case IDENT:

				i = tableManager.Position(wordAnalysis.getWord());

				if (i == 0) {
					errorManager.addError(11);

				} else if (table[i].getType() != TableType.VARIABLE) {
					errorManager.addError(12);

				} else {

					getSymbol();
					if (sym == Symbol.BECOMES) {
						getSymbol();

					} else {
						errorManager.addError(13);
					}

					expression(symbolSet);
					if (i > 0) {
						targetCodeManager.addCode(Fct.STO, level - table[i].getLevel(), table[i].getAddress());
					}
				}
				break;

			case READSYM:

				getSymbol();

				if (sym != Symbol.LPAREN) {
					errorManager.addError(34);

				} else
					do {
						getSymbol();
						i = (sym == Symbol.IDENT) ? tableManager.Position(wordAnalysis.getWord()) : 0;

						if (i == 0)
							errorManager.addError(11);
						else if (table[i].getType() == TableType.CONSTANT) {
							errorManager.addError(33);

						} else {
							targetCodeManager.addCode(Fct.OPR, 0, 16);
							targetCodeManager.addCode(Fct.STO, level - table[i].getLevel(), table[i].getAddress());
						}
						getSymbol();

					} while (sym == Symbol.COMMA);

				if (sym != Symbol.RPAREN) {
					errorManager.addError(33);

					while (!symbolSet.contains(sym)) {
						getSymbol();
					}

				} else {
					getSymbol();
				}
				break;


			case WRITESYM:

				getSymbol();

				if (sym == Symbol.LPAREN) {

					do {
						getSymbol();
						s.clear();
						s.addAll(symbolSet);
						s.add(Symbol.RPAREN);
						s.add(Symbol.COMMA);
						expression(s);
						targetCodeManager.addCode(Fct.OPR, 0, 14);

					} while (sym == Symbol.COMMA);

					if (sym != Symbol.RPAREN) {
						errorManager.addError(33);

					} else {
						getSymbol();

					}
				} else {
					errorManager.addError(28);
				}
				targetCodeManager.addCode(Fct.OPR, 0, 15);
				break;


			case CALLSYM:

				getSymbol();

				if (sym != Symbol.IDENT) {
					errorManager.addError(14);

				} else {
					i = tableManager.Position(wordAnalysis.getWord());

					if (i == 0) {
						errorManager.addError(11);

					} else if (table[i].getType() == TableType.PROCEDURE) {
						targetCodeManager.addCode(Fct.CAL, level - table[i].getLevel(), table[i].getAddress());

					} else {
						errorManager.addError(15);
					}
					getSymbol();
				}
				break;

			case IFSYM:

				s.clear();
				s.addAll(symbolSet);
				s.add(Symbol.THENSYM);
				s.add(Symbol.DOSYM);
				getSymbol();
				condition(s);

				if (sym == Symbol.THENSYM) {
					getSymbol();

				} else {
					errorManager.addError(16);
				}

				index1 = targetCodeManager.getCodeIndex();
				targetCodeManager.addCode(Fct.JPC, 0, 0);
				s.clear();
				s.addAll(symbolSet);
				s.add(Symbol.ELSESYM);
				statement(s);

				if (sym == Symbol.ELSESYM) {

					getSymbol();
					index2 = targetCodeManager.getCodeIndex();
					targetCodeManager.addCode(Fct.JMP, 0, 0);

					statement(symbolSet);
					targetCodeManager.code[index1].setAddress(index2 + 1);
					targetCodeManager.code[index2].setAddress(targetCodeManager.getCodeIndex());

				} else {
					targetCodeManager.code[index1].setAddress(targetCodeManager.getCodeIndex());
				}
				break;

			case BEGINSYM:

				getSymbol();
				s.clear();
				s.addAll(symbolSet);
				s.add(Symbol.SEMICOLON);
				s.add(Symbol.ENDSYM);
				statement(s);

				while (stateBeginSym.contains(sym) || sym == Symbol.SEMICOLON) {

					if (sym == Symbol.SEMICOLON) {
						getSymbol();

					} else {
						errorManager.addError(10);
					}

					statement(s);
				}

				if (sym == Symbol.ENDSYM) {
					getSymbol();

				} else {
					errorManager.addError(17);
				}
				break;

			case WHILESYM:

				index1 = targetCodeManager.getCodeIndex();
				getSymbol();
				s.clear();
				s.addAll(symbolSet);
				s.add(Symbol.DOSYM);
				condition(s);

				index2 = targetCodeManager.getCodeIndex();
				targetCodeManager.addCode(Fct.JPC, 0, 0);

				if (sym == Symbol.DOSYM) {
					getSymbol();

				} else {
					errorManager.addError(18);
				}

				statement(symbolSet);
				targetCodeManager.addCode(Fct.JMP, 0, index1);
				targetCodeManager.code[index2].setAddress(targetCodeManager.getCodeIndex());

				break;

			case REPEATSYM:

				getSymbol();
				index1 = targetCodeManager.getCodeIndex();

				while (sym != Symbol.UNTILSYM && !stateBeginSym.contains(sym) && sym != Symbol.ENDSYM) {

					s.clear();
					s.addAll(symbolSet);
					s.add(Symbol.SEMICOLON);
					s.add(Symbol.UNTILSYM);
					statement(s);

					if (sym == Symbol.SEMICOLON) {
						getSymbol();
					}

				}
				if (sym == Symbol.UNTILSYM) {
					getSymbol();
					s.clear();
					s.addAll(symbolSet);
					s.add(Symbol.SEMICOLON);
					condition(s);
					targetCodeManager.addCode(Fct.JPC, 0, index1);

				} else {
					errorManager.addError(25);
				}
				break;

			default:
				s.clear();
				modify(symbolSet, s, 19);
				break;
		}

	}

	private void expression(Set<Symbol> symbolSet) {
		Symbol symbol;
		HashSet<Symbol> symset = new HashSet<Symbol>();

		if (sym == Symbol.PLUS || sym == Symbol.MINUS) {

			symbol = sym;
			getSymbol();

			symset.addAll(symbolSet);
			symset.add(Symbol.PLUS);
			symset.add(Symbol.MINUS);
			term(symset);

			if (symbol == Symbol.MINUS) {

				targetCodeManager.addCode(Fct.OPR, 0, 1);
			}

		} else {

			symset.addAll(symbolSet);
			symset.add(Symbol.PLUS);
			symset.add(Symbol.MINUS);
			term(symset);
		}

		while (sym == Symbol.PLUS || sym == Symbol.MINUS) {

			symbol = sym;
			getSymbol();
			
			symset.addAll(symbolSet);
			symset.add(Symbol.PLUS);
			symset.add(Symbol.MINUS);

			term(symset);

			if (symbol == Symbol.PLUS) {
				targetCodeManager.addCode(Fct.OPR, 0, 2);

			} else {
				targetCodeManager.addCode(Fct.OPR, 0, 3);
			}
		}
	}

	private void term(Set<Symbol> symbolSet) {
		Symbol mulop;
		Set<Symbol> symset = new HashSet<Symbol>();

		symset.addAll(symbolSet);
		symset.add(Symbol.TIMES);
		symset.add(Symbol.SLASH);
		factor(symset);

		while (sym == Symbol.TIMES || sym == Symbol.SLASH) {

			mulop = sym;
			getSymbol();
			factor(symset);

			if (mulop == Symbol.TIMES) {
				targetCodeManager.addCode(Fct.OPR, 0, 4);

			} else {
				targetCodeManager.addCode(Fct.OPR, 0, 5);
			}
		}
	}

	private void factor(Set<Symbol> symbolSet) {
		int i;
		Table[] table = tableManager.getTable();
		modify(expBeginSym, symbolSet, 24);

		while (expBeginSym.contains(sym)) {

			switch (sym) {

				case IDENT:

					i = tableManager.Position(wordAnalysis.getWord());

					if (i == 0) {
						errorManager.addError(11);

					} else {

						switch (table[i].getType()) {

							case CONSTANT:
								targetCodeManager.addCode(Fct.LIT, 0, table[i].getVal());
								break;

							case VARIABLE:
								targetCodeManager.addCode(Fct.LOD, level - table[i].getLevel(), table[i].getAddress());
								break;

							case PROCEDURE:
								errorManager.addError(21);
								break;
						}
					}
					getSymbol();
					break;

				case NUMBER:

					if (wordAnalysis.num > WordAnalysis.MAX_NUMBER) {
						errorManager.addError(31);
						wordAnalysis.num = 0;
					}

					targetCodeManager.addCode(Fct.LIT, 0, wordAnalysis.num);
					getSymbol();
					break;

				case LPAREN:

					getSymbol();
					symbolSet.add(Symbol.RPAREN);
					expression(symbolSet);

					if (sym == Symbol.RPAREN) {
						getSymbol();

					} else {
						errorManager.addError(22);
					}

					modify(symbolSet, expBeginSym, 23);
					break;
			}

		}
	}

	private void condition(Set<Symbol> symbolSet) {
		Symbol relop;
		HashSet<Symbol> s = new HashSet<Symbol>();

		if (sym == Symbol.ODDSYM) {
			getSymbol();
			expression(symbolSet);
			targetCodeManager.addCode(Fct.OPR, 0, 6);

		} else {
			s.clear();
			s.addAll(symbolSet);
			s.add(Symbol.EQL);
			s.add(Symbol.NEQ);
			s.add(Symbol.LSS);
			s.add(Symbol.LEQ);
			s.add(Symbol.GTR);
			s.add(Symbol.GEQ);
			expression(s);

			if (sym != Symbol.EQL && sym != Symbol.NEQ && sym != Symbol.LSS && sym != Symbol.LEQ && sym != Symbol.GTR && sym != Symbol.GEQ)
				errorManager.addError(20);

			else {
				relop = sym;
				getSymbol();
				expression(symbolSet);

				switch (relop) {
					case EQL:
						targetCodeManager.addCode(Fct.OPR, 0, 8);
						break;

					case NEQ:
						targetCodeManager.addCode(Fct.OPR, 0, 9);
						break;

					case LSS:
						targetCodeManager.addCode(Fct.OPR, 0, 10);
						break;

					case GEQ:
						targetCodeManager.addCode(Fct.OPR, 0, 11);
						break;

					case GTR:
						targetCodeManager.addCode(Fct.OPR, 0, 12);
						break;

					case LEQ:
						targetCodeManager.addCode(Fct.OPR, 0, 13);
						break;
				}
			}
		}
	}

	public int getDataAddress() {
		return data[level];
	}

	public int getLevel() {
		return level;
	}

	private void getSymbol() {
		sym = wordAnalysis.getSymbol();
	}
}